package lsh;

import com.google.common.hash.*;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import javax.sql.*;

public class LSH {
	
	
	private HashFunction[] hashFunctions;
	private int bands;
	private int hashPerBand;
	private int shingleSize;
	
	private String dbUrl;
	private Connection con=null;
	
	private Connection getConnection() throws ClassNotFoundException, SQLException
	{
		if(con==null)
		{
			Class.forName("com.mysql.jdbc.Driver");
			this.con = DriverManager.getConnection (this.dbUrl, "lsh", "lsh");
		}
		return this.con;
	}
	
	public void createTables() throws ClassNotFoundException, SQLException
	{
		for(int i=0; i<this.bands; i++)
		{
			String sql="CREATE TABLE IF NOT EXISTS `band"+i+"` (\n"+
					  "`uid` int(11) NOT NULL,\n";
			for(int j=0; j<this.hashPerBand; j++)
			{
				sql+="`h"+j+"` int(11) NOT NULL,\n";
			}
			sql+=" PRIMARY KEY (`uid`),\n"+
					"KEY `h1` (`h1`,`h2`)\n"+
					") ENGINE=MyISAM DEFAULT CHARSET=latin1;";
			this.getConnection().createStatement().execute(sql);
		}
	}
	
	public LSH(int bands, int hashPerBand, int shingleSize, String dbUrl)
	{
		this.dbUrl=dbUrl;
		this.bands=bands;
		this.hashPerBand=hashPerBand;
		this.shingleSize=shingleSize;
		hashFunctions=new HashFunction[bands*hashPerBand];
		for( int i=0; i<bands*hashPerBand; i++)
		{
			hashFunctions[i]=Hashing.murmur3_32(i);
		}
	}
	
	private String[] shingling(String doc)
	{
		int numShingles=doc.length()-this.shingleSize+1;
		String[] shingles=new String[Math.max(0, numShingles)];
		for(int i=0; i<numShingles; i++)
		{
			shingles[i]=doc.substring(i, i+this.shingleSize);
		}
		return shingles;
	}
	
	public void put(long uid, String doc) throws ClassNotFoundException, SQLException
	{
		int[] minhashes=minHashes(this.shingling(doc));
		this.save(uid, minhashes);
	}

	public long[] query(String doc) throws ClassNotFoundException, SQLException
	{
		HashSet<Long> matches=new HashSet<Long>();
		int[] minhashes=minHashes(this.shingling(doc));
		for(int j=0; j<this.bands; j++)
		{
			String sql="SELECT uid FROM band"+j+" WHERE ";
			String sep="";
			for(int i=0; i<this.hashPerBand; i++)
			{
				sql+=sep+"h"+i+"="+minhashes[j*this.hashPerBand+i];
				sep=" AND ";
			}
			sql+=";";
			ResultSet rs=this.getConnection().createStatement().executeQuery(sql);
			while(rs.next())
			{
				matches.add(rs.getLong(1));
			}
		}
		
		long[] matchesArray=new long[matches.size()];
		Iterator<Long> it=matches.iterator();
		int i=0;
		while(it.hasNext())
		{
			matchesArray[i]=it.next();
			i++;
		}
		return matchesArray;
	}
	
	
	private int[] minHashes(String[] shingles) {
		int[] minhashes=new int[this.hashFunctions.length];
		for(int j=0; j<this.hashFunctions.length; j++)
		{
			minhashes[j]=Integer.MAX_VALUE;
		}
		for(int i=0; i<shingles.length; i++)
		{
			for(int j=0; j<this.hashFunctions.length; j++)
			{
				int val=this.hashFunctions[j].newHasher().putString(shingles[i]).hash().asInt();
				if(val<minhashes[j])
				{
					minhashes[j]=val;
				}
			}
		}
		
		return minhashes;
	}
	
	private void save(long uid, int[] minhashes) throws ClassNotFoundException, SQLException
	{
		for(int j=0; j<this.bands; j++)
		{
			String sql="INSERT INTO band"+j+" VALUES("+uid;
			for(int i=0; i<this.hashPerBand; i++)
			{
				sql+=", "+minhashes[j*this.hashPerBand+i];
			}
			sql+=") ON DUPLICATE KEY UPDATE ";
			String sep="";
			for(int i=0; i<this.hashPerBand; i++)
			{
				sql+=sep+"h"+i+"="+minhashes[j*this.hashPerBand+i];
				sep=", ";
			}
			sql+=";";
			this.getConnection().createStatement().execute(sql);
		}
		
	}
	
	

}
